package com.qt.myboot.aspect;

import java.lang.reflect.Method;

import com.qt.myboot.annotation.RedissonLock;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.spring.starter.RedissonAutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;


/**
 * @author xusl
 * @date 2019/06/13 13:11:22
 * @version 1.0.0
 */
@Aspect
@Component
//@ConditionalOnBean(RedissonClient.class)
@AutoConfigureAfter(RedissonAutoConfiguration.class)
@Slf4j
public class RedissonDistributedLockAspect {

	private RedissonClient redissonClient;

	private ExpressionParser parser = new SpelExpressionParser();

	private LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();

	public RedissonDistributedLockAspect(RedissonClient redissonClient) {
		this.redissonClient = redissonClient;
	}

	@Pointcut("@annotation(com.qt.myboot.annotation.RedissonLock)")
	private void lockPoint(){

	}

	@Around("lockPoint()")
	public Object around(ProceedingJoinPoint pjp) throws Throwable{
		Method method = ((MethodSignature) pjp.getSignature()).getMethod();
		RedissonLock lockAction = method.getAnnotation(RedissonLock.class);
		String key = lockAction.value();
		Object[] args = pjp.getArgs();
		key = parse(key, method, args);

		RLock lock = getLock(key, lockAction);
		if(!lock.tryLock(lockAction.waitTime(), lockAction.leaseTime(), lockAction.unit())) {
			log.info("get lock failed [{}]", key);
			return "毛也么得";
		}

		//得到锁,执行方法，释放锁
		log.info("get lock success [{}]", key);
        Object result = pjp.proceed();
        lock.unlock();
        log.info("release lock [{}]", key);
		return result;
	}

	/**
	 * @description 解析spring EL表达式
	 * @author xusl
	 * @date 2019/06/13 16:41:01
	 * @version 1.0.0
	 * @param key 表达式
	 * @param method 方法
	 * @param args 方法参数
	 * @return
	 */
	private String parse(String key, Method method, Object[] args) {
		String[] params = discoverer.getParameterNames(method);
		EvaluationContext context = new StandardEvaluationContext();
		for (int i = 0; i < params.length; i ++) {
			context.setVariable(params[i], args[i]);
		}
		return parser.parseExpression(key).getValue(context, String.class);
	}

	private RLock getLock(String key, RedissonLock redissonLock) {
		switch (redissonLock.lockType()) {
			case REENTRANT_LOCK:
				return redissonClient.getLock(key);

			case FAIR_LOCK:
				return redissonClient.getFairLock(key);

			case READ_LOCK:
				return redissonClient.getReadWriteLock(key).readLock();

			case WRITE_LOCK:
				return redissonClient.getReadWriteLock(key).writeLock();

			default:
				throw new RuntimeException("do not support lock type:" + redissonLock.lockType().name());
			}
	}
}